package com.yingxin.yx.framework.commons.id;


import java.util.Calendar;
import java.util.Date;

/**
 * @author fanchao
 */
public class SnowflakePartition {


    /**
     * 开始时间截 (2015-01-01)
     */
    private final long twepoch = 1420041600000L;

    /**
     * 机器id所占的位数
     */
    private final long workerIdBits = 5L;

    /**
     * 数据标识id所占的位数
     */
    private final long datacenterIdBits = 5L;

    /**
     * 支持的最大机器id，结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
     */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

    /**
     * 支持的最大数据标识id，结果是31
     */
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

    /**
     * 序列在id中占的位数
     */
    private final long sequenceBits = 12L;

    /**
     * 机器ID向左移12位
     */
    private final long workerIdShift = sequenceBits;

    /**
     * 数据标识id向左移17位(12+5)
     */
    private final long datacenterIdShift = sequenceBits + workerIdBits;

    /**
     * 时间截向左移22位(5+5+12)
     */
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    /**
     * 工作机器ID(0~31)
     */
    private long workerId;

    /**
     * 数据中心ID(0~31)
     */
    private long datacenterId;

    /**
     * 毫秒内序列(0~4095)
     */
    private long sequence = 0L;

    /**
     * 构造函数
     */
    private SnowflakePartition(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    // &运算中1&1=1,其余为0
    // |运算中0|0=0,其余为1
    // 9<<4 左位移4位

    private synchronized long nextId(long timestamp) {
        //移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - twepoch) << timestampLeftShift)
                | (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift)
                | sequence;
    }

    public static long convertId(Date date) {
        SnowflakePartition snowflakePartition = new SnowflakePartition(0, 0);
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return snowflakePartition.nextId(calendar.getTimeInMillis());
    }

    public static long leftOfMin(Date date) {
        SnowflakePartition snowflakePartition = new SnowflakePartition(0, 0);
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.SECOND, 0);
        return snowflakePartition.nextId(calendar.getTimeInMillis());
    }

    public static long rightOfMin(Date date) {
        SnowflakePartition snowflakePartition = new SnowflakePartition(31, 31);
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.SECOND, 59);
        return snowflakePartition.nextId(calendar.getTimeInMillis());
    }

    public static long leftOfDay(Date date) {
        SnowflakePartition snowflakePartition = new SnowflakePartition(0, 0);
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        return snowflakePartition.nextId(calendar.getTimeInMillis());
    }

    public static long rightOfDay(Date date) {
        SnowflakePartition snowflakePartition = new SnowflakePartition(31, 31);
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.HOUR_OF_DAY, 23);
        calendar.set(Calendar.MINUTE, 59);
        calendar.set(Calendar.SECOND, 59);
        calendar.set(Calendar.MILLISECOND, 999);
        return snowflakePartition.nextId(calendar.getTimeInMillis());
    }

    /**
     * date - days
     *
     * @param date
     * @param days
     * @return
     */
    public static long left(Date date, int days) {
        SnowflakePartition snowflakePartition = new SnowflakePartition(0, 0);
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.DATE, days * -1);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        return snowflakePartition.nextId(calendar.getTimeInMillis());
    }

    /**
     * date + days
     *
     * @param date
     * @param days
     * @return
     */
    public static long right(Date date, int days) {
        SnowflakePartition snowflakePartition = new SnowflakePartition(31, 31);
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.DATE, days);
        calendar.set(Calendar.HOUR_OF_DAY, 23);
        calendar.set(Calendar.MINUTE, 59);
        calendar.set(Calendar.SECOND, 59);
        calendar.set(Calendar.MILLISECOND, 999);
        return snowflakePartition.nextId(calendar.getTimeInMillis());
    }

}
